package com.example.ortools.start.cp;

import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import org.apache.logging.log4j.util.Strings;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MIPDealDlyEmample {

    public static void main(String[] args) {


        //规格id最大数量限制
        Map<Long, BigDecimal> specIdMaxNumLimit = new HashMap<>();
        //规格id拆零单位数据
        Map<Long, Integer> specIdSingleNum = new HashMap<>();
        //规格id最大整包数量
        Map<Long, Integer> specIdMaxIntNum = new HashMap<>();
        List<MergeData> req = new ArrayList<>();

        cpSolver(specIdMaxIntNum, req);

    }

    public static void cpSolver(Map<Long, Integer> specIdMaxIntNum, List<MergeData> req) {
        //加载求解器原生库
        Loader.loadNativeLibraries();

        Map<Integer, List<MergeData>> listMap = req.stream().collect(Collectors.groupingBy(MergeData::getGroupId));
        for (Map.Entry<Integer, List<MergeData>> listEntry : listMap.entrySet()) {
            //创建MIP模型求解器
            MPSolver solver = MPSolver.createSolver("SCIP");
            //初始化变量存储集合
            Map<String,MIPTempHelper> allVarMap = new HashMap<>();
            //初始化共享变量存储集合
            Map<Long,List<MPVariable>> sharedVarMap = new HashMap<>();
            //初始化变量名称与拆零单位映射集合
            Map<String,BigDecimal> valNameBySingleNumMap = new HashMap<>();
            for (MergeData mergeData : listEntry.getValue()) {
                //初始化变量
                initMPVariable(specIdMaxIntNum, solver, allVarMap, sharedVarMap, valNameBySingleNumMap, mergeData);
                //设置系数限制条件
                setRatioCoefficient(solver, allVarMap, mergeData);
            }
            //设置共享限制条件
            setSharedCofficient(specIdMaxIntNum, solver, sharedVarMap);


            //构造求解目标
            MPObjective objective = makeMPObjective(solver, allVarMap);

            //求解输出
            executeSolver(solver, allVarMap, objective);
        }
    }

    private static void executeSolver(MPSolver solver, Map<String, MIPTempHelper> allVarMap, MPObjective objective) {
        final MPSolver.ResultStatus status = solver.solve();
        if (status == MPSolver.ResultStatus.OPTIMAL) {
            System.out.printf("Maximum of objective function: %f%n", objective.value());
            for (MIPTempHelper value : allVarMap.values()) {
                MPVariable var = value.getVar();
                System.out.println(var.name() + ": " + new BigDecimal(Double.toString(var.solutionValue())).multiply(value.getSplitUseRatio()));
            }
            System.out.println("\nAdvanced usage:");
            System.out.println("Problem solved in " + solver.wallTime() + " milliseconds");
            System.out.println("Problem solved in " + solver.iterations() + " iterations");

        } else {
            System.out.println("No solution found.");
        }
    }

    private static MPObjective makeMPObjective(MPSolver solver, Map<String, MIPTempHelper> allVarMap) {
        MPObjective objective = solver.objective();
        for (MIPTempHelper value : allVarMap.values()) {
            objective.setCoefficient(value.getVar(), value.getSplitUseRatio().doubleValue());
        }
        objective.setMaximization();
        return objective;
    }

    private static void setSharedCofficient(Map<Long, Integer> specIdMaxIntNum, MPSolver solver, Map<Long, List<MPVariable>> sharedVarMap) {
        for (Map.Entry<Long, List<MPVariable>> entry : sharedVarMap.entrySet()) {
            MPConstraint c0 = solver.makeConstraint(0, specIdMaxIntNum.get(entry.getKey()), "sharedTemp"+entry.getKey());
            for (MPVariable var : entry.getValue()) {
                c0.setCoefficient(var, 1);
            }
        }
    }

    private static void setRatioCoefficient(MPSolver solver, Map<String, MIPTempHelper> allVarMap, MergeData mergeData) {
        for(int i = 1; i< mergeData.getGoodsSpecCombinationROList().size(); i++){
            GoodsSpecCombinationRO pre = mergeData.getGoodsSpecCombinationROList().get(i-1);
            GoodsSpecCombinationRO cur = mergeData.getGoodsSpecCombinationROList().get(i);
            MPVariable preVar = null;
            MPVariable curVar = null;
            String tempValName = Strings.EMPTY;
            if(pre.getShareSpecId()!=null) {
                preVar = allVarMap.get(tempValName+pre.getGoodsSpecId() + pre.getSeq()).getVar();
            }else{
                preVar = allVarMap.get(tempValName+pre.getGoodsSpecId()).getVar();
            }
            if(cur.getShareSpecId()!=null) {
                curVar = allVarMap.get(tempValName+cur.getGoodsSpecId() + cur.getSeq()).getVar();
            }else{
                curVar = allVarMap.get(tempValName+cur.getGoodsSpecId()).getVar();
            }
            MPConstraint c0 = solver.makeConstraint(0, 0, "temp"+i);
            c0.setCoefficient(preVar,cur.getRatio().multiply(pre.getSplitUseRatio()).doubleValue());
            c0.setCoefficient(curVar,-pre.getRatio().multiply(cur.getSplitUseRatio()).doubleValue());
        }
    }

    private static void initMPVariable(Map<Long, Integer> specIdMaxIntNum, MPSolver solver, Map<String, MIPTempHelper> allVarMap, Map<Long, List<MPVariable>> sharedVarMap, Map<String, BigDecimal> valNameBySingleNumMap, MergeData mergeData) {
        for (GoodsSpecCombinationRO bo : mergeData.getGoodsSpecCombinationROList()) {
            String tempValName = Strings.EMPTY;
            MPVariable tempVar = null;
            if(bo.getShareSpecId()==null){
                tempValName = tempValName + bo.getGoodsSpecId();
                tempVar = solver.makeIntVar(0, specIdMaxIntNum.get(bo.getGoodsSpecId()), tempValName);
            }else{
                tempValName = tempValName + bo.getGoodsSpecId()+bo.getSeq();
                tempVar = solver.makeIntVar(0, specIdMaxIntNum.get(bo.getGoodsSpecId()), tempValName);
                if(!sharedVarMap.containsKey(bo.getShareSpecId())){
                    sharedVarMap.put(bo.getShareSpecId(),new ArrayList<>());
                }
                sharedVarMap.get(bo.getShareSpecId()).add(tempVar);
            }
            MIPTempHelper tempHelper = new MIPTempHelper();
            tempHelper.setVar(tempVar);
            tempHelper.setSplitUseRatio(bo.getSplitUseRatio());
            allVarMap.put(tempValName,tempHelper);
            valNameBySingleNumMap.put(tempValName,bo.getSplitUseRatio());
        }
    }


}
